home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr47 / tsrsrc34.zip / RELEASE.PAS < prev    next >
Pascal/Delphi Source File  |  1993-04-17  |  22KB  |  694 lines

  1. {**************************************************************************
  2. *   RELEASE - Releases memory above the last MARK call made.              *
  3. *   Copyright (c) 1986,1991 Kim Kokkonen, TurboPower Software.            *
  4. *   May be freely distributed and used but not sold except by permission. *
  5. ***************************************************************************
  6. *   Version 1.0 2/8/86                                                    *
  7. *     original public release                                             *
  8. *     (thanks to Neil Rubenking for an outline of the method used)        *
  9. *   :                                                                     *
  10. *   long intervening history                                              *
  11. *   :                                                                     *
  12. *   Version 3.0 9/24/91                                                   *
  13. *     make compatible with DOS 5                                          *
  14. *     add Quiet option                                                    *
  15. *     close open file handles of released blocks                          *
  16. *     update for new WATCH behavior                                       *
  17. *     increase number of supported memory blocks to 256                   *
  18. *     add support for upper memory blocks                                 *
  19. *   Version 3.1 11/4/91                                                   *
  20. *     no change                                                           *
  21. *   Version 3.2 11/22/91                                                  *
  22. *     generalize method of accessing high memory                          *
  23. *     reverse order in which memory blocks are released to work           *
  24. *       correctly with the 386MAX high memory manager                     *
  25. *     merge blocks in high memory after release (QEMM doesn't)            *
  26. *   Version 3.3 1/8/92                                                    *
  27. *     add /H to use high memory optionally                                *
  28. *     new features for parsing and getting command line options           *
  29. *   Version 3.4 2/14/92                                                   *
  30. *     fix hang that occurs when QEMM LOADHI didn't have space to          *
  31. *       load a mark high                                                  *
  32. ***************************************************************************
  33. *   telephone: 719-260-6641, CompuServe: 76004,2611.                      *
  34. *   requires Turbo version 6 to compile.                                  *
  35. ***************************************************************************}
  36.  
  37. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  38. {$M 16384,0,655360}
  39.  
  40. program ReleaseTSR;
  41.   {-Restore system to state it had when a MARK was placed}
  42.  
  43. uses
  44.   Dos,
  45.   MemU,
  46.   Ems,
  47.   Xms;
  48.  
  49. var
  50.   Blocks : BlockArray;
  51.   markBlock, BlockMax : BlockType;
  52.   markPsp : Word;
  53.   CommandSeg : Word;
  54.   StartMcb : Word;
  55.   HiMemSeg : Word;
  56.  
  57.   markName : String[127];
  58.  
  59.   ReturnCode : Word;
  60.   OptUseHiMem, UseHiMem, DealWithEMS, KeepMark,
  61.   MemMark, FilMark, Quiet : Boolean;
  62.   Keys : string[16];
  63.  
  64.   TrappedBytes : LongInt;
  65.  
  66.   MarkEHandles : Word;
  67.   CurrEHandles : Word;
  68.   MarkEmsHandles : PageArrayPtr;
  69.   CurrEmsHandles : PageArrayPtr;
  70.  
  71.   {Save areas read in from file mark}
  72.   Vectors : array[0..1023] of Byte;
  73.   EGAsavTable : array[0..7] of Byte;
  74.   IntComTable : array[0..15] of Byte;
  75.   ParentSeg : Word;
  76.   ParentLen : Word;
  77.   McbP : ^McbGroup;
  78.  
  79.   procedure Abort(msg : String);
  80.     {-Halt in case of error}
  81.   begin
  82.     WriteLn(msg);
  83.     Halt(1);
  84.   end;
  85.  
  86.   procedure NoRestoreHalt(ReturnCode : Word);
  87.     {-Replace Turbo halt with one that doesn't restore any interrupts}
  88.   begin
  89.     Close(Output);
  90.     asm
  91.       mov ah,$4C
  92.       mov al, byte(ReturnCode)
  93.       int $21
  94.     end;
  95.   end;
  96.  
  97.   function FindMark(markName, MarkID : String;
  98.                     MarkOffset : Word;
  99.                     var MemMark, FilMark : Boolean;
  100.                     var b : BlockType) : Boolean;
  101.     {-Find the last memory block matching idstring at offset idoffset}
  102.   var
  103.     BPsp : Word;
  104.     PassedFileMark : Boolean;
  105.  
  106.     function HasIDstring(segment : Word;
  107.                          idString : String;
  108.                          idOffset : Word) : Boolean;
  109.       {-Return true if idstring is found at segment:idoffset}
  110.     var
  111.       len : Byte;
  112.       tString : String;
  113.     begin
  114.       len := Length(idString);
  115.       tString[0] := Chr(len);
  116.       Move(Mem[segment:idOffset], tString[1], len);
  117.       HasIDstring := (tString = idString);
  118.     end;
  119.  
  120.     function GetMarkName(segment : Word) : String;
  121.       {-Return a cleaned up mark name from the segment's PSP}
  122.     var
  123.       tString : String;
  124.       tlen : Byte absolute tString;
  125.     begin
  126.       Move(Mem[segment:$80], tString[0], 128);
  127.       while (tlen > 0) and ((tString[1] = ' ') or (tString[1] = ^I)) do
  128.         Delete(tString, 1, 1);
  129.       while (tlen > 0) and ((tString[tlen] = ' ') or (tString[tlen] = ^I)) do
  130.         dec(tlen);
  131.       GetMarkName := StUpcase(tString);
  132.     end;
  133.  
  134.     function MatchMemMark(segment : Word;
  135.                           markName : String;
  136.                           var b : BlockType) : Boolean;
  137.       {-Return true if MemMark is unnamed or matches current name}
  138.     var
  139.       FoundIt : Boolean;
  140.       tString : String;
  141.     begin
  142.       tString := GetMarkName(segment);
  143.       if markName <> '' then begin
  144.         FoundIt := (tString = markName);
  145.         if not FoundIt and not UseHiMem then
  146.           if (tString <> '') and (tString[1] = ProtectChar) then
  147.             {Current mark is protected, stop searching}
  148.             b := 1;
  149.       end else if (tString <> '') and (tString[1] = ProtectChar) then begin
  150.         {Stored mark name is protected}
  151.         FoundIt := False;
  152.         {Stop checking}
  153.         b := 1;
  154.       end else if tString = '' then
  155.         {Unnamed release and unnamed mark}
  156.         FoundIt := True
  157.       else begin
  158.         {Unnamed release and named mark, match only if didn't pass file mark}
  159.         FoundIt := not PassedFileMark;
  160.         {Stop searching if no match}
  161.         if not FoundIt then
  162.           B := 1;
  163.       end;
  164.       if not FoundIt then
  165.         dec(b);
  166.       MatchMemMark := FoundIt;
  167.     end;
  168.  
  169.     function MatchFilMark(segment : Word;
  170.                           markName : String;
  171.                           var b : BlockType) : Boolean;
  172.       {-Return true if FilMark is unnamed or matches current name}
  173.     var
  174.       FoundIt : Boolean;
  175.     begin
  176.       if markName <> '' then begin
  177.         FoundIt := (GetMarkName(segment) = markName);
  178.         if FoundIt then
  179.           {Assure named file exists}
  180.           FoundIt := ExistFile(markName);
  181.       end else begin
  182.         {File marks must be named on RELEASE command line}
  183.         FoundIt := False;
  184.         PassedFileMark := True;
  185.       end;
  186.       if not FoundIt then
  187.         dec(B);
  188.       MatchFilMark := FoundIt;
  189.     end;
  190.  
  191.   begin
  192.     {Scan from the last block down to find the last MARK TSR}
  193.     b := BlockMax;
  194.     MemMark := False;
  195.     FilMark := False;
  196.     PassedFileMark := False;
  197.     repeat
  198.       BPsp := Blocks[B].Psp;
  199.       if (Blocks[B].Mcb+1 <> BPsp) or (BPsp = PrefixSeg) then
  200.         {Don't match any non-program block or this program}
  201.         dec(b)
  202.       else if HasIDstring(BPsp, NmarkID, NmarkOffset) then begin
  203.         {A net mark, can't release it here}
  204.         if UseHiMem then
  205.           {Keep looking}
  206.           dec(b)
  207.         else
  208.           {Stop looking}
  209.           b := 0;
  210.       end else if HasIDstring(BPsp, MarkID, MarkOffset) then
  211.         {An in-memory mark}
  212.         MemMark := MatchMemMark(BPsp, markName, b)
  213.       else if HasIDstring(BPsp, FmarkID, FmarkOffset) then
  214.         {A file mark}
  215.         FilMark := MatchFilMark(BPsp, markName, b)
  216.       else
  217.         {Not a mark}
  218.         dec(b);
  219.     until (b < 1) or MemMark or FilMark;
  220.     FindMark := MemMark or FilMark;
  221.   end;
  222.  
  223.   procedure ReadMarkFile(markName : String);
  224.     {-Read the mark file info into memory}
  225.   var
  226.     McbCount : Word;
  227.     f : file;
  228.   begin
  229.     Assign(f, markName);
  230.     Reset(f, 1);
  231.     if IoResult <> 0 then
  232.       Abort('Error opening mark file');
  233.  
  234.     {Read the vector table from the mark file, into a temporary memory area}
  235.     BlockRead(f, Vectors, 1024);
  236.  
  237.     {Read the BIOS miscellaneous save areas into temporary tables}
  238.     BlockRead(f, EGAsavTable, 8);
  239.     BlockRead(f, IntComTable, 16);
  240.     BlockRead(f, ParentSeg, 2);
  241.     BlockRead(f, ParentLen, 2);
  242.  
  243.     {Read the stored EMS handles, if any}
  244.     BlockRead(f, MarkEHandles, SizeOf(Word));
  245.     GetMem(MarkEmsHandles, SizeOf(HandlePageRecord)*MarkEHandles);
  246.     BlockRead(f, MarkEmsHandles^, SizeOf(HandlePageRecord)*MarkEHandles);
  247.  
  248.     {Read the stored Mcb table}
  249.     BlockRead(f, McbCount, SizeOf(Word));
  250.     GetMem(McbP, SizeOf(Word)+2*SizeOf(Word)*McbCount);
  251.     BlockRead(f, McbP^.Mcbs, 2*SizeOf(Word)*McbCount);
  252.     McbP^.Count := McbCount;
  253.  
  254.     if IoResult <> 0 then
  255.       Abort('Error reading mark file');
  256.     Close(f);
  257.  
  258.     if not KeepMark then
  259.       {Delete the mark file so it causes no mischief later}
  260.       Erase(f);
  261.   end;
  262.  
  263.   procedure InitMarkInfo;
  264.     {-Set up information from mark in memory}
  265.   begin
  266.     MarkEHandles := MemW[markPsp:EMScntOffset];
  267.     MarkEmsHandles := Ptr(markPsp, EMSmapOffset);
  268.     McbP := Ptr(markPsp, EMSmapOffset+4*MarkEHandles);
  269.   end;
  270.  
  271.   procedure CopyVectors;
  272.     {-Put interrupt vectors back into table}
  273.   var
  274.     PSeg : Word;
  275.     PLen : Word;
  276.   begin
  277.     IntsOff;
  278.  
  279.     {Restore the main interrupt vector table}
  280.     if FilMark then
  281.       Move(Vectors, Mem[0:0], 1024)
  282.     else
  283.       Move(Mem[markPsp:VectorOffset], Mem[0:0], 1024);
  284.  
  285.     IntsOn;
  286.  
  287.     {Restore misc save areas}
  288.     if FilMark then begin
  289.       Move(EGAsavTable, Mem[$40:$A8], 8);
  290.       Move(IntComTable, Mem[$40:$F0], 16);
  291.       PSeg := ParentSeg;
  292.       PLen := ParentLen;
  293.     end else begin
  294.       Move(Mem[markPsp:EGAsavOffset], Mem[$40:$A8], 8);
  295.       Move(Mem[markPsp:IntComOffset], Mem[$40:$F0], 16);
  296.       PSeg := MemW[markPsp:ParentOffset];
  297.       PLen := MemW[markPsp:ParLenOffset];
  298.     end;
  299.  
  300.     {Restore the parent address}
  301.     if ValidPsp(HiMemSeg, PSeg, PLen) then begin
  302.       {Don't restore parent if it no longer exists (applies to QEMM LOADHI)}
  303.       MemW[PrefixSeg:$16] := PSeg;
  304.       if not UseHiMem then
  305.         {Programs loaded into high memory have strange termination addresses}
  306.         Move(Mem[0:4*$22], Mem[PrefixSeg:$0A], 4); {Int 22 addresses}
  307.     end;
  308.  
  309.     {Move the old break/error addresses into this program}
  310.     Move(Mem[0:4*$23], Mem[PrefixSeg:$0E], 8); {Int 23,24 addresses}
  311.   end;
  312.  
  313.   procedure MarkBlocks(markBlock : BlockType);
  314.     {-Mark those blocks to be released}
  315.  
  316.     procedure BatchWarning(b : BlockType);
  317.       {-Warn about the trapping effect of batch files}
  318.     var
  319.       t : BlockType;
  320.     begin
  321.       WriteLn('Memory space for TSRs installed prior to batch file');
  322.       WriteLn('will not be released until batch file completes.');
  323.       WriteLn;
  324.       ReturnCode := 1;
  325.       {Accumulate number of bytes temporarily trapped}
  326.       for t := 1 to b do
  327.         if Blocks[t].releaseIt then
  328.           inc(TrappedBytes, LongInt(MemW[Blocks[t].mcb:3]) shl 4);
  329.     end;
  330.  
  331.     procedure MarkBlocksAbove;
  332.       {-Mark blocks above the mark}
  333.     var
  334.       b : BlockType;
  335.     begin
  336.       for b := 1 to BlockMax do
  337.         with Blocks[b] do
  338.           if (b >= markBlock) and (psp = CommandSeg) then begin
  339.             {Don't release blocks owned by master COMMAND.COM}
  340.             releaseIt := False;
  341.             BatchWarning(b);
  342.           end else if KeepMark then
  343.             {Release all but RELEASE and the mark}
  344.             releaseIt := (psp <> PrefixSeg) and (psp > markPsp)
  345.           else
  346.             releaseIt := (psp <> PrefixSeg) and (psp >= markPsp);
  347.     end;
  348.  
  349.     procedure MarkUnallocatedBlocks;
  350.       {-Mark blocks that weren't allocated at time of mark}
  351.     var
  352.       TopSeg : Word;
  353.       b : BlockType;
  354.       m : BlockType;
  355.       Found : Boolean;
  356.     begin
  357.       {Find last low memory mcb}
  358.       TopSeg := TopOfMemSeg-1;
  359.       m := 1;
  360.       Found := False;
  361.       while (not Found) and (m <= McbP^.Count) do
  362.         if McbP^.Mcbs[m].mcb >= TopSeg then
  363.           Found := True
  364.         else
  365.           inc(m);
  366.  
  367.       {Mark out all mcbs associated with psp of last low memory mcb}
  368.       TopSeg := McbP^.Mcbs[m-1].psp;
  369.       if TopSeg <> markPsp then
  370.         for m := 1 to McbP^.Count do
  371.           with McbP^.Mcbs[m] do
  372.             if psp = TopSeg then
  373.               psp := 0;
  374.  
  375.       for b := 1 to BlockMax do
  376.         with Blocks[b] do begin
  377.           Found := False;
  378.           m := 1;
  379.           while (not Found) and (m <= McbP^.Count) do begin
  380.             Found := (McbP^.Mcbs[m].psp <> 0) and (McbP^.Mcbs[m].mcb = mcb);
  381.             inc(m);
  382.           end;
  383.           if Found then
  384.             {was allocated at time of mark, keep it now unless a mark to be released}
  385.             releaseIt := not KeepMark and (psp = markPsp)
  386.           else if psp = CommandSeg then
  387.             {Don't release blocks owned by master COMMAND.COM}
  388.             releaseIt := False
  389.           else
  390.             {not allocated at time of mark}
  391.             releaseIt := (psp <> 0) and (psp <> PrefixSeg);
  392.         end;
  393.     end;
  394.  
  395.   begin
  396.     if UseHiMem then
  397.       MarkUnallocatedBlocks
  398.     else
  399.       MarkBlocksAbove;
  400.  
  401.     {$IFDEF Debug}
  402.     for b := 1 to BlockMax do
  403.       with Blocks[b] do
  404.         WriteLn(b:3, ' ', HexW(psp), ' ', HexW(mcb), ' ', releaseIt);
  405.     {$ENDIF}
  406.   end;
  407.  
  408.   function ReleaseBlock(Segm : Word) : Word; assembler;
  409.     {-Use DOS services to release memory block}
  410.   asm
  411.     mov ah,$49
  412.     mov es,Segm
  413.     int $21
  414.     jc  @Done
  415.     xor ax,ax
  416. @Done:
  417.   end;
  418.  
  419.   procedure ReleaseMem;
  420.     {-Release DOS memory marked for release}
  421.   var
  422.     B : BlockType;
  423.   begin
  424.     for B := BlockMax downto 1 do
  425.       with Blocks[B] do
  426.         if releaseIt then
  427.           if ReleaseBlock(mcb+1) <> 0 then begin
  428.             WriteLn('Could not release block at segment ', HexW(mcb+1));
  429.             Abort('Memory may be a mess... Please reboot');
  430.           end;
  431.     MergeHiMemBlocks(HiMemSeg);
  432.   end;
  433.  
  434.   procedure SetPSP(PSP : Word); assembler;
  435.     {-Sets current PSP}
  436.   asm
  437.     mov bx,psp
  438.     mov ax,$5000
  439.     int $21
  440.   end;
  441.  
  442.   procedure CloseHandles;
  443.     {-Close any handles of blocks marked for release}
  444.   type
  445.     HandleTable = array[0..65520] of Byte;
  446.   var
  447.     O : Word;
  448.     FileMax : Word;
  449.     TablePtr : ^HandleTable;
  450.     b : BlockType;
  451.     H : Byte;
  452.   begin
  453.     for b := 1 to BlockMax do
  454.       with Blocks[b] do
  455.         if releaseIt and (psp = mcb+1) and (memw[psp:0] = $20CD) then begin
  456.           {A released block with a program segment prefix}
  457.           {set psp to this block}
  458.           setpsp(psp);
  459.  
  460.           {Deal with expanded handle tables in DOS 3.0 and later}
  461.           if DosV >= 3 then begin
  462.             FileMax := MemW[Psp:$32];
  463.             TablePtr := Pointer(MemL[Psp:$34]);
  464.           end else begin
  465.             FileMax := 20;
  466.             TablePtr := Ptr(Psp, $18);
  467.           end;
  468.  
  469.           for O := 0 to FileMax-1 do begin
  470.             H := TablePtr^[O];
  471.             case H of
  472.               0, 1, 2, $FF : {standard handle or not open} ;
  473.             else
  474.               asm
  475.                 mov ah,$3E
  476.                 mov bx,O
  477.                 int $21      {ignore errors}
  478.               end;
  479.             end;
  480.           end;
  481.         end;
  482.  
  483.     {reset psp}
  484.     setpsp(prefixseg);
  485.   end;
  486.  
  487.   procedure RestoreEMSmap;
  488.     {-Restore EMS to state at time of mark}
  489.   var
  490.     O, N, NHandle : Word;
  491.  
  492.     procedure EmsError;
  493.     begin
  494.       WriteLn('Program error or EMS device not responding');
  495.       Abort('EMS memory may be a mess... Please reboot');
  496.     end;
  497.  
  498.   begin
  499.     {Get the existing EMS page map}
  500.     GetMem(CurrEmsHandles, MaxHandles*SizeOf(HandlePageRecord));
  501.     CurrEHandles := EmsHandles(CurrEmsHandles^);
  502.  
  503.     if CurrEHandles > MaxHandles then
  504.       WriteLn('EMS handle count exceeds capacity of RELEASE -- no action taken')
  505.  
  506.     else if CurrEHandles <> 0 then begin
  507.       {Compare the two maps and deallocate pages not in the stored map}
  508.       for N := 1 to CurrEHandles do begin
  509.         {Scan all current handles}
  510.         NHandle := CurrEmsHandles^[N].Handle;
  511.         if MarkEHandles > 0 then begin
  512.           {See if current handle matches one stored by MARK}
  513.           O := 1;
  514.           while (MarkEmsHandles^[O].Handle <> NHandle) and (O <= MarkEHandles) do
  515.             Inc(O);
  516.           {If not, deallocate the current handle}
  517.           if (O > MarkEHandles) then
  518.             if not FreeEms(NHandle) then
  519.               EmsError;
  520.         end else
  521.           {No handles stored by MARK, deallocate all current handles}
  522.           if not FreeEms(NHandle) then
  523.             EmsError;
  524.       end;
  525.     end;
  526.   end;
  527.  
  528.   procedure GetOptions;
  529.     {-Analyze command line for options}
  530.  
  531.     procedure WriteCopyright;
  532.     begin
  533.       WriteLn('RELEASE ', Version, ', Copyright 1991 TurboPower Software');
  534.     end;
  535.  
  536.     procedure WriteHelp;
  537.       {-Show the options}
  538.     begin
  539.       WriteCopyright;
  540.       WriteLn;
  541.       WriteLn('RELEASE removes memory-resident programs from memory and restores the');
  542.       WriteLn('interrupt vectors to their state as found prior to the installation of a MARK.');
  543.       WriteLn('RELEASE manages both normal DOS memory and also Lotus/Intel Expanded Memory.');
  544.       WriteLn('If WATCH has been installed, RELEASE will update the WATCH data area for the');
  545.       WriteLn('TSRs released.');
  546.       WriteLn;
  547.       WriteLn('RELEASE accepts the following command line syntax:');
  548.       WriteLn;
  549.       WriteLn('  RELEASE [MarkName] [Options]');
  550.       WriteLn;
  551.       WriteLn('Options may be preceded by either / or -. Valid options are as follows:');
  552.       WriteLn;
  553.       WriteLn('  /E         do NOT access EMS memory.');
  554.       WriteLn('  /H         work with upper memory if available.');
  555.       WriteLn('  /K         release memory, but keep the mark in place.');
  556.       WriteLn('  /Q         write no screen output.');
  557.       WriteLn('  /S chars   stuff string (<16 chars) into keyboard buffer on exit.');
  558.       WriteLn('  /U         work with upper memory, but halt if none found.');
  559.       WriteLn('  /?         write this help screen.');
  560.       WriteLn;
  561.       WriteLn('When /U is requested, a MarkName must always be specified.');
  562.       Halt(1);
  563.     end;
  564.  
  565.     procedure GetArgs(S : String);
  566.     var
  567.       SPos : Word;
  568.       Arg : String[127];
  569.     begin
  570.       SPos := 1;
  571.       repeat
  572.         Arg := NextArg(S, SPos);
  573.         if Arg = '' then
  574.           Exit;
  575.         if Arg[1] = '?' then
  576.           WriteHelp
  577.         else if (Arg[1] = '-') or (Arg[1] = '/') then
  578.           case Length(Arg) of
  579.             1 : Abort('Missing command option following '+Arg);
  580.             2 : case UpCase(Arg[2]) of
  581.                   '?' : WriteHelp;
  582.                   'E' : DealWithEMS := False;
  583.                   'H' : OptUseHiMem := True;
  584.                   'K' : KeepMark := True;
  585.                   'Q' : Quiet := True;
  586.                   'S' : begin
  587.                           Arg := NextArg(S, SPos);
  588.                           if Length(Arg) = 0 then
  589.                             Abort('Key string missing');
  590.                           if Length(Arg) > 15 then
  591.                             Abort('No more than 15 keys may be stuffed');
  592.                           Keys := Arg+^M;
  593.                         end;
  594.                   'U' : UseHiMem := True;
  595.                 else
  596.                   Abort('Unknown command option: '+Arg);
  597.                 end;
  598.           else
  599.             Abort('Unknown command option: '+Arg);
  600.           end
  601.         else
  602.           {Named mark}
  603.           markName := StUpcase(Arg);
  604.       until False;
  605.     end;
  606.  
  607.   begin
  608.     {Initialize defaults}
  609.     markName := '';
  610.     Keys := '';
  611.     ReturnCode := 0;
  612.     TrappedBytes := 00;
  613.  
  614.     KeepMark := False;
  615.     Quiet := False;
  616.     DealWithEMS := True;
  617.     UseHiMem := False;
  618.     OptUseHiMem := False;
  619.  
  620.     {Get arguments from the command line and the environment}
  621.     GetArgs(StringPtr(Ptr(PrefixSeg, $80))^);
  622.     GetArgs(GetEnv('RELEASE'));
  623.  
  624.     if not Quiet then
  625.       WriteCopyright;
  626.  
  627.     {Initialize for high memory access}
  628.     if OptUseHiMem or UseHiMem then begin
  629.       HiMemSeg := FindHiMemStart;
  630.       if HiMemSeg = 0 then begin
  631.         if UseHiMem then
  632.           Abort('No upper memory blocks found');
  633.       end else
  634.         UseHiMem := True;
  635.     end else
  636.       HiMemSeg := 0;
  637.  
  638.     if UseHiMem then
  639.       if MarkName = '' then
  640.         Abort('Upper memory releases must refer to named marks');
  641.   end;
  642.  
  643. begin
  644.   {Analyze command line for options}
  645.   GetOptions;
  646.  
  647.   {Get all allocated memory blocks in normal memory}
  648.   FindTheBlocks(True, HiMemSeg, Blocks, BlockMax, StartMcb, CommandSeg);
  649.  
  650.   {Find the last one marked with the MARK idstring, and MarkName if specified}
  651.   if not FindMark(markName, MarkID, MarkOffset, MemMark, FilMark, markBlock) then
  652.     Abort('No matching marker found, or protected marker encountered.');
  653.   markPsp := Blocks[markBlock].psp;
  654.  
  655.   {Get file mark information into memory}
  656.   if FilMark then
  657.     ReadMarkFile(markName)
  658.   else
  659.     InitMarkInfo;
  660.  
  661.   {Mark those blocks to be released}
  662.   MarkBlocks(markBlock);
  663.  
  664.   {Copy the vector table from the MARK copy}
  665.   CopyVectors;
  666.  
  667.   {Close open file handles}
  668.   CloseHandles;
  669.  
  670.   {Release normal memory marked for release}
  671.   ReleaseMem;
  672.  
  673.   {Deal with expanded memory}
  674.   if DealWithEMS then
  675.     if EMSpresent then
  676.       RestoreEMSmap;
  677.  
  678.   {Write success message}
  679.   if not Quiet then begin
  680.     Write('Memory released after MARK');
  681.     if markName <> '' then
  682.       Write(' (', markName, ')');
  683.     WriteLn;
  684.     if ReturnCode <> 0 then
  685.       WriteLn(TrappedBytes, ' bytes temporarily trapped until batch file completes');
  686.   end;
  687.  
  688.   {Stuff keyboard buffer if requested}
  689.   if Length(Keys) > 0 then
  690.     StuffKeys(Keys, True);
  691.  
  692.   NoRestoreHalt(ReturnCode);
  693. end.
  694.